#!/usr/bin/env python3
from typing import List, Optional, Tuple
from pathlib import Path
import json
from utils.ssh_client import Client


class DockerConfig:
    """Docker 配置数据类
    
    属性:
        registry_url: 镜像仓库地址 (例如: harbor.example.com)
        keep_images: 保留最近的镜像数量
        default_tag: 默认的镜像标签
    """
    def __init__(self, registry_url: str, keep_images: int = 5, default_tag: str = 'latest'):
        self.registry_url = registry_url
        self.keep_images = keep_images
        self.default_tag = default_tag
    
    def __repr__(self) -> str:
        return f"DockerConfig(registry_url='{self.registry_url}', keep_images={self.keep_images}, default_tag='{self.default_tag}')"


class DockerManager:
    """Docker 容器和镜像管理器
    
    这个类提供了 Docker 容器和镜像的完整管理功能，包括：
    1. 容器的启动、停止、删除
    2. 镜像的拉取、删除
    3. 状态检查和错误处理
    """
    
    def __init__(self, ssh_client: Client, config: DockerConfig):
        """初始化 Docker 管理器
        
        参数:
            ssh_client: SSH 客户端实例
            config: Docker 配置对象
        """
        self.ssh = ssh_client
        self.config = config
        # self._login_registry()
    
    def _login_registry(self) -> None:
        """登录 Docker 镜像仓库"""
        if self.config.registry_username and self.config.registry_password:
            cmd = (
                f"docker login {self.config.registry_url} "
                f"-u {self.config.registry_username} "
                f"-p {self.config.registry_password}"
            )
            state, _, stderr = self.ssh.remote_command(cmd)
            if state != 0:
                raise Exception(f"Docker 仓库登录失败: {stderr}")
    
    def _check_container_exists(self, container_name: str) -> bool:
        """检查容器是否存在
        
        参数:
            container_name: 容器名称
        
        返回:
            bool: 容器是否存在
        """
        cmd = f"docker ps -a --format '{{{{.Names}}}}' | grep '^{container_name}$'"
        state, _, _ = self.ssh.remote_command(cmd)
        return state == 0
    
    def _check_container_running(self, container_name: str) -> bool:
        """检查容器是否正在运行
        
        参数:
            container_name: 容器名称
        
        返回:
            bool: 容器是否正在运行
        """
        cmd = f"docker ps --format '{{{{.Names}}}}' | grep '^{container_name}$'"
        state, _, _ = self.ssh.remote_command(cmd)
        return state == 0
    
    def _get_container_id(self, container_name: str) -> Optional[str]:
        """获取容器 ID
        
        参数:
            container_name: 容器名称
        
        返回:
            str: 容器 ID，如果容器不存在则返回 None
        """
        cmd = f"docker ps -a --format '{{{{.ID}}}}' --filter name=^{container_name}$"
        state, stdout, _ = self.ssh.remote_command(cmd)
        return stdout.strip() if state == 0 else None
    
    def start_container(self, container_name: str, run_cmd: str) -> None:
        """启动容器
        
        如果容器已存在且正在运行，则跳过。
        如果容器存在但未运行，则启动它。
        如果容器不存在，则创建并启动。
        
        参数:
            container_name: 容器名称
            run_cmd: docker run 命令参数
        """
        
        # 如果容器正在运行，直接返回
        if self._check_container_running(container_name):
            print(f"容器 {container_name} 已经在运行")
            return
        
        # 如果容器存在但没有运行，启动它
        if self._check_container_exists(container_name):
            cmd = f"docker start {container_name}"
            state, _, stderr = self.ssh.remote_command(cmd)
            if state != 0:
                raise Exception(f"启动容器失败: {stderr}")
            print(f"容器 {container_name} 已启动")
            return
        
        # 创建并启动新容器
        state, _, stderr = self.ssh.remote_command(run_cmd)
        if state != 0:
            raise Exception(f"创建容器失败: {stderr}")
        print(f"容器 {container_name} 已创建并启动")
    
    def stop_container(self, container_name: str) -> None:
        """停止容器
        
        如果容器不存在或已经停止，则跳过。
        
        参数:
            container_name: 容器名称
        """
        if not self._check_container_running(container_name):
            print(f"容器 {container_name} 已经停止或不存在")
            return
        
        cmd = f"docker stop {container_name}"
        state, _, stderr = self.ssh.remote_command(cmd)
        if state != 0:
            raise Exception(f"停止容器失败: {stderr}")
        print(f"容器 {container_name} 已停止")
    
    def remove_container(self, container_name: str, force: bool = False) -> None:
        """删除容器
        
        如果容器正在运行且 force=True，则先停止再删除。
        如果容器正在运行且 force=False，则抛出异常。
        
        参数:
            container_name: 容器名称
            force: 是否强制删除（即使容器正在运行）
        """
        if not self._check_container_exists(container_name):
            print(f"容器 {container_name} 不存在")
            return
        
        if self._check_container_running(container_name):
            if force:
                self.stop_container(container_name)
            else:
                raise Exception(f"容器 {container_name} 正在运行，请先停止")
        
        cmd = f"docker rm {container_name}"
        state, _, stderr = self.ssh.remote_command(cmd)
        if state != 0:
            raise Exception(f"删除容器失败: {stderr}")
        print(f"容器 {container_name} 已删除")
    
    def pull_image(self, image_name: str) -> None:
        """拉取镜像
        
        参数:
            image_name: 镜像名称
        """
        cmd = f"docker pull {image_name}"
        state, _, stderr = self.ssh.remote_command(cmd)
        if state != 0:
            raise Exception(f"拉取镜像失败: {stderr}")
        print(f"镜像 {image_name} 已拉取")
    
    def clean_images(self, project_name: str, keep: Optional[int] = None) -> None:
        """清理旧镜像
        
        保留最新的几个版本，删除其他版本。
        
        参数:
            project_name: 项目名称（用于过滤镜像）
            keep: 保留的版本数量，默认使用配置中的值
        """
        keep = keep or self.config.keep_images
        
        # 获取所有相关镜像，按创建时间排序
        cmd = f"docker images --format '{{{{.ID}}}} {{{{.Repository}}}} {{{{.Tag}}}} {{{{.CreatedAt}}}}' | grep {project_name} | sort -k4,4r"
        state, stdout, _ = self.ssh.remote_command(cmd)
        if state != 0:
            return
        
        images = stdout.strip().split('\n')
        if len(images) <= keep:
            return
        
        # 删除旧镜像
        for image in images[keep:]:
            if not image:
                continue
            image_id = image.split()[0]
            cmd = f"docker rmi {image_id}"
            self.ssh.remote_command(cmd)
        
        print(f"已清理 {project_name} 的旧镜像，保留最新的 {keep} 个版本")
    
    def get_container_logs(self, container_name: str, tail: int = 100) -> str:
        """获取容器日志
        
        参数:
            container_name: 容器名称
            tail: 获取最后几行日志
        
        返回:
            str: 容器日志
        """
        cmd = f"docker logs --tail {tail} {container_name}"
        _, stdout, _ = self.ssh.remote_command(cmd)
        return stdout
    
    def get_container_status(self, container_name: str) -> dict:
        """获取容器状态信息
        
        参数:
            container_name: 容器名称
        
        返回:
            dict: 容器状态信息
        """
        cmd = f"docker inspect {container_name}"
        state, stdout, stderr = self.ssh.remote_command(cmd)
        if state != 0:
            raise Exception(f"获取容器状态失败: {stderr}")
        
        container_info = json.loads(stdout)[0]
        return {
            'id': container_info['Id'],
            'name': container_info['Name'].lstrip('/'),
            'status': container_info['State']['Status'],
            'running': container_info['State']['Running'],
            'started_at': container_info['State']['StartedAt'],
            'image': container_info['Config']['Image'],
            'ports': container_info['NetworkSettings']['Ports']
        }
    
    def get_latest_image(self, project_name: str) -> Optional[str]:
        """获取项目最新的镜像
        
        参数:
            project_name: 项目名称
            
        返回:
            str: 最新镜像的完整名称，如果没有找到则返回 None
        """
        # 使用 --format 获取镜像信息，并按创建时间排序
        cmd = f"docker images --format '{{{{.Repository}}}}:{{{{.Tag}}}} {{{{.CreatedAt}}}}' | grep {project_name} | sort -k2,3 -r | head -n 1 | awk '{{print $1}}'"
        state, stdout, _ = self.ssh.remote_command(cmd)
        if state != 0 or not stdout.strip():
            return None
        return stdout.strip()